# coding=utf-8
# author: Feng YongMing
import concurrent.futures
import functools
import logging
import queue
from multiprocessing import Pool, Manager, cpu_count
from roxe_libs.pub_function import setCustomLogger, sendTestResultMail, split_log
from roxe_libs.Global import Global
from roxe_libs.BeautifulReport import BeautifulReport, testResultFields
from roxe_libs import settings
import unittest
import datetime
import argparse
import sys
import os
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed, ProcessPoolExecutor


# 当前脚本所在目录   os.path.abspath返回文件的绝对路径，返回一个path对象的路径
cur_path = os.path.abspath(os.path.dirname(__file__))

origin_stdout = sys.stdout
origin_stderr = sys.stderr
#重定向信息 详细参考学习989


# 测试用例类的导入配置信息 执行的用例集每次自己写了一个新的用例集就在这里配置一下才能执行
#RMN.RMNApiTest.RMNApiTest   RMN文件夹下的RMNApiTest.py文件的RMNApiTest类（class RMNApiTest()...）
test_class_config = {
    "hysApiTest": "hys.hysApiTest.RMNApiTest",
}

#写入到/log文件夹下调用roxe_libs.pub_function.py 的setCustomLogger函数，来控制日志输出
logger = setCustomLogger("test", "./log/apiTest.log", isprintsreen=True, logRedirectHandler=True, logfilemod="w")

Global.setValue(settings.logger_name, logger.name)
Global.setValue(settings.enable_trace, True)
#设置全局变量



class RunCase:
    load_strategy = "keyword_load"  # 默认查找用例的策略名称
    class_config = {}
    find_case_strategy = {}

    # 保存测试结果的数据结构，在生成html报告发送邮件时有用到
    testResultDict = testResultFields()

    #__init__私有化，类外边用不了  __init__中的第一个self是 类RunCase本身， 相当于java中this
    def __init__(self, case_classes: dict, env="bjtest", debug=True, argv=None):
        self.env = env
        #测试环境，默认给的是bjtest

        Global.setValue(settings.environment, env)

        self.report_dir = os.path.abspath(os.path.join(cur_path, "TestReports"))
        self.reportPath = None
        self.start_time = None



        self.run_suite = unittest.TestSuite()



        self.parser = None
        self.argv = argv if argv else sys.argv[1::] #拆解见学习85行
        #sys.argv 见学习996




        self._initArgs()
        # 默认的用例加载策略
        self.registerStrategy(self.load_strategy, self.singleLoadCase)  #self.load_strategy = 'keyword_load'传给strategy_name
        #self.registerStrategy 是这个class里的 registerStrategy函数，然后把self.load_strategy, self.singleLoadCase传进去了   等于是直接运行了??
        #load_strategy 是用例执行策略     定义为keyword_load
        #self.singleLoadCase  直接调用了singleLoadCase函数
        for case_class, class_loader_name in case_classes.items():
            #case_classes.items() 传进来的是test_class_config值
            self.registerTestClass(case_class, class_loader_name)



    #声明一个parser（解析器）  -----------argparse.ArgumentParser()
    #命令行参数解析    parser.add_argument创建一个新的参数
    def _initArgs(self):
        parser = argparse.ArgumentParser()
        parser.add_argument("-t", "--testClass", help="测试类, 多个类以','分隔", type=str, dest="testClass", action="store")
        parser.add_argument("-c", "--case", help="测试用例查找的关键字, 执行多个用例以','分隔", type=str, dest="caseKey", default="", action="store")
        parser.add_argument("-r", help="执行用例的策略, 现支持: multi_run、debug_class、debug", type=str, dest="runWay", action="store")
        self.parser = parser.parse_args(self.argv)
        #add_argument设置完参数之后 要通过 parser.parse_args进行解析
        logger.debug(f"参数为: testClass: {self.parser.testClass} caseKey:{self.parser.caseKey} runWay:{self.parser.runWay}")

    #init里有用到
    def registerTestClass(self, test_class, name):
        """
        注册用例类的方法
        :param test_class:
        :param name:
        """
        self.class_config[test_class] = name


    # init里有用到
    def registerStrategy(self, strategy_name, strategy_func):
        """
        注册加载测试用例类策略的方法
        :param strategy_name: 加载用例的策略名称
        :param strategy_func: 加载用例的策略方法
        :return:
        """
        self.find_case_strategy[strategy_name] = strategy_func
        """
        for i, j in self.find_case_strategy.items():
            print ("\n")
            print(i, j)
        """
    #用例执行策略用
    @staticmethod
    def singleLoadCase(all_cases, key_val, class_val=""):
        """
        根据关键字中找出想要的case放入要执行的测试suite中
        :param all_cases: 必须为TestCase组成的suite类，不能是嵌套的suite类
        :param key_val: 搜索用例的关键字
        :param class_val: 搜索测试类的关键字
        """
        return [c for c in all_cases for key in key_val.split(",") if key in str(c) and class_val in str(c)]
        #拆解一下,结果如下

        for c in all_cases:
            for key in key_val.split(","):
                if key in str(c) and class_val in str(c):
                    return [c]


    #用在run中，应该很重要w
    def loadCase(self, test_class, case_key="", load_strategy=None):
        find_cases = []
        for each_class in test_class.split(","):
            each_find = unittest.TestLoader().loadTestsFromName(self.class_config[each_class])
            #print (each_find)   #<unittest.suite.TestSuite tests=[<RMN.RMNApiTest.RMNApiTest testMethod=test_023_sn_sn_cdtrAgtGiveSNRoxeId_sameCurrency>]>
            #print (type(each_find))    #<class 'unittest.suite.TestSuite'>
            #上见学习999
            #下见学习995
            for i in iter(each_find):
                find_cases.append(i)
                #[<unittest.loader._FailedTest testMethod=RMNApiTest>]

        if load_strategy is None:
            # 默认使用注册的用例查找策略
            load_strategy = self.load_strategy
        # 查找加载用例的策略
        try:
            stra_func = self.find_case_strategy[load_strategy] #self.find_case_strategy 是字典 stra_func 是singleLoadCase()这个函数的内存地址，指向了singleLoadCase函数
            #print(stra_func)
            #print(type(stra_func))
        except KeyError:
            raise ValueError("找不到对应的查找用例策略")

        if case_key:
            find_cases = stra_func(find_cases, case_key)
        self.run_suite.addTests(find_cases)
        #见学习993
    #策略为debug时调用 普通执行脚本默认执行这个
    def runCaseWithTextRunner(self):
        runner = unittest.TextTestRunner(verbosity=2)
        #见学习998
        self.start_time = datetime.datetime.now()
        runner.run(self.run_suite)




    #main直接调这个    strategy默认是debug
    def run(self, test_class, case_key="", strategy="debug", load_case_strategy=None):
        """
        执行用例的主方法
        :param test_class: 运行的测试类
        :param case_key: 需要进行筛选加载的case关键字
        :param strategy: 运行策略：multi_run[并发执行, 结果发送邮件]、debug_class[非并发运行, 结果发送邮件]、默认为调试用
        :param load_case_strategy: 用例加载策略：默认为关键字查找
        :return:
        """
        # 加载用例
        self.loadCase(test_class, case_key, load_case_strategy)
        #跳转到函数loadCase
        logger.debug(f"准备运行的用例: {self.run_suite}")
        print("\n冯泳铭查看", f"准备运行的用例: {self.run_suite}")
        run_strategy = {
            #"debug_class": functools.partial(self.runCaseWithHtmlReport, test_class=test_class),
            "debug": self.runCaseWithTextRunner,
        }
        if strategy not in run_strategy.keys():
            raise ValueError(f"当前仅支持的运行策略: {[i for i in run_strategy.keys()]}")

        run_strategy[strategy]()
        #执行其中的函数！！！等于是运行 self.runCaseWithTextRunner()


if __name__ == "__main__":

    run_client = RunCase(test_class_config, "sandbox")
    #将类RunCase实例化



    run_client.run("RMNApiTest" ,  "test_023")
